import Map from "../map";
import Log from "../../utils/log";
import {MapSnapshot} from "./history";
import Utils from "../../utils/utils";
import {Event} from "./events";

/**
 * Manage map image exports.
 */
export default class Export {

    private map: Map;

    /**
     * Get the associated map instance.
     * @param {Map} map
     */
    constructor(map: Map) {
        this.map = map;
    }

    /**
     * Return the snapshot (json) of the current map.
     * @returns {MapSnapshot} json
     */
    public asJSON = (): MapSnapshot => {
        let snapshot = this.map.history.current();

        this.map.events.call(Event.exportJSON);

        return  Utils.cloneObject(snapshot);
    };

    /**
     * Return the image data URI in the callback function.
     * @param {Function} callback
     * @param {string} type
     */
    public asImage = (callback: Function, type?: string) => {
        if (typeof callback !== "function") {
            Log.error("The first parameter must be a function", "type");
        }

        if (type && typeof type !== "string") {
            Log.error("The second optional parameter must be a string", "type");
        }

        this.map.nodes.deselectNode();

        this.dataURI(url => {
            let image = new Image();

            image.src = url;

            image.onload = () => {
                let canvas = document.createElement("canvas"),
                    context = canvas.getContext("2d");

                canvas.width = image.width;
                canvas.height = image.height;
                context.drawImage(image, 0, 0);
                context.globalCompositeOperation = "destination-over";
                context.fillStyle = "#ffffff";
                context.fillRect(0, 0, canvas.width, canvas.height);

                if (typeof type === "string") {
                    type = "image/" + type;
                }

                callback(canvas.toDataURL(type));
                this.map.events.call(Event.exportImage);
            };

            image.onerror = () => {
                Log.error("The image has not been loaded correctly");
            };
        });
    };

    /**
     * Convert the mind map svg in the data URI.
     * @param {Function} callback
     */
    private dataURI(callback: Function) {
        let element = this.map.dom.g.node(),
            clone = element.cloneNode(true),
            svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"),
            box = element.getBBox(),
            css = Utils.cssRules(element),
            xmlns = "http://www.w3.org/2000/xmlns/",
            padding = 15,
            x = box.x - padding, y = box.y - padding,
            w = box.width + padding * 2, h = box.height + padding * 2;

        svg.setAttributeNS(xmlns, "xmlns", "http://www.w3.org/2000/svg");
        svg.setAttributeNS(xmlns, "xmlns:xlink", "http://www.w3.org/1999/xlink");
        svg.setAttribute("version", "1.1");
        svg.setAttribute("width", w);
        svg.setAttribute("height", h);
        svg.setAttribute("viewBox", [x, y, w, h].join(" "));

        // If there is css, insert it
        if (css !== "") {
            let style = document.createElement("style"),
                defs = document.createElement("defs");

            style.setAttribute("type", "text/css");
            style.innerHTML = "<![CDATA[\n" + css + "\n]]>";
            defs.appendChild(style);
            svg.appendChild(defs);
        }

        clone.setAttribute("transform", "translate(0,0)");
        svg.appendChild(clone);

        this.convertImages(clone, () => {
            let xmls = new XMLSerializer(),
                reader = new FileReader();

            let blob = new Blob([
                xmls.serializeToString(svg)
            ], {
                type: "image/svg+xml"
            });

            reader.readAsDataURL(blob);

            reader.onloadend = () => {
                callback(reader.result);
            };
        });
    }

    /**
     * If there are images in the map convert their href in dataURI.
     * @param {HTMLElement} element
     * @param {Function} callback
     */
    private convertImages(element: HTMLElement, callback: Function) {
        let images = element.querySelectorAll("image"),
            counter = images.length;

        if (counter > 0) {
            for (let image of <any>images) {
                let canvas = document.createElement("canvas"),
                    ctx = canvas.getContext("2d"),
                    img = new Image(),
                    href = image.getAttribute("href");

                img.crossOrigin = "Anonymous";

                img.src = href;

                img.onload = function () {
                    canvas.width = img.width;
                    canvas.height = img.height;
                    ctx.drawImage(img, 0, 0);

                    image.setAttribute("href", canvas.toDataURL("image/png"));

                    counter--;

                    if (counter === 0) {
                        callback();
                    }
                };
                img.onerror = () => {
                    counter--;

                    if (counter === 0) {
                        callback();
                    }
                };

            }
        } else {
            callback();
        }
    }

}